iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

一些讓你看來很強的全端- trcp 伴讀系列 第 21

Day-021. 一些讓你看來很強的全端 TRPC 伴讀 -Middleware

  • 分享至 

  • xImage
  •  

終於說了那麼多next-auth篇幅現在可以談主角trpc 了,有了next-authsession 內容,我們就可以把它放到 trpccontext 拉~

trpc middleware

express 概念一樣 middleware 就是讓你來做身份驗證的,但我們要怎麼拿到 user 身份內容呢?不知道讀者還記不記得我們可以在 client 端透過 useSession 中查看 user session 內容,但其實next-auth有提供 server-sidefunction 讓你拿到 session 他可以用在 SSR 或是 api route 甚至是 server enver environment

getServerSideProps

import { authOptions } from 'pages/api/auth/[...nextauth]'
import { getServerSession } from "next-auth/next"

export async function getServerSideProps(context) {
  const session = await getServerSession(context.req, context.res, authOptions)

  if (!session) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    }
  }

  return {
    props: {
      session,
    },
  }
}

API route

import { authOptions } from 'pages/api/auth/[...nextauth]'
import { getServerSession } from "next-auth/next"


export default async function handler(req, res) {
  const session = await getServerSession(req, res, authOptions)

  if (!session) {
    res.status(401).json({ message: "You must be logged in." });
    return;
  }

  return res.json({
    message: 'Success',
  })
}

但如果是在 trpc 你需要先定義 getServerAuthSession

//~src/server/auth

import { getServerSession } from 'next-auth'
export const getServerAuthSession = (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  return getServerSession(ctx.req, ctx.res, authOptions);
};

之後 getServerAuthSession 會放到 createTRPCContext 中把 session 傳到 trpc context

//~src/server/api/trpc
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
  const { req, res } = opts
  const session = await getServerAuthSession({ req, res });
  return {
    session,
    prisma
  };
};

createTRPCContextopts params 則是因為在 ~src/api/trpc/[trpc].ts create 一個 api route,所以才會有 nextreqres 參數。

export default createNextApiHandler({
  router: appRouter,
  createContext: createTRPCContext,
});

因為我們有添加 sessiontrpccontect ,所以實作 middleware 時,我們就可以判斷 ctx 中是否有 user info,接著就可以訂一個 protectProcedure 包含enforceUserIsAuthed middleware

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' })
  }
  return next({
    ctx: {
      session: { ...ctx.session, user: ctx.session.user },
    }
  })
})

export const publicProcedure = t.procedure;
export const protectProcedure = t.procedure.use(enforceUserIsAuthed);

接著我們把 protectProcedure 替換 publicProcedure

export const postsRouter = router({
  infinitePosts: protectProcedure
    .input(getInfinitePostSchema)
    //..

addPost 部分也要。

addPost: protectProcedure
    .input(createPostSchema)
    .mutation(async ({ input, ctx }) => {
      const { prisma, session } = ctx
      const { title } = input
      const duplicatePost = await prisma.post.findFirst({
        where: {
          title: title
        }
      })
      //..
      return { message: 'success create post' }
    }),

然後到 PostForm 中我們加個 toast error

export const PostForm = ({ updateSuccessCallBack }: PostFormProps) => {
  const utils = api.useContext()
  const { mutateAsync: createPost } = api.posts.addPost.useMutation({
    onSuccess: () => {
      // utils.posts.getPosts.invalidate()
      utils.posts.infinitePosts.refetch()
      if (updateSuccessCallBack) {
        updateSuccessCallBack()
      }
    },
    onError: (e) => {
      if (e instanceof TRPCClientError) {
        toast.error(e.message)
      }
    }
  })

然後我們到 http://localhost:3000/posts 隨便新增 post,這時你就會發現有 error 摟~日後假如你的 router 希望有身份限制就用 protectProcedure

https://ithelp.ithome.com.tw/upload/images/20231005/20145677bndmCKbtbs.png

修改 add post

那現在 addPost 改成 protectProcedure,所以我們可以透過 ctx 拿到 session

 addPost: protectProcedure
    .input(createPostSchema)
    .mutation(async ({ input, ctx }) => {
      const { prisma, session } = ctx

接著我們將 session user.id 關聯到我們的 post 中,如此一來只要user 是登入狀態,create post 後就會自動關聯到 user.id

addPost: protectProcedure
    .input(createPostSchema)
    .mutation(async ({ input, ctx }) => {
      const { prisma, session } = ctx
      const { title } = input
      const duplicatePost = await prisma.post.findFirst({
        where: {
          title: title
        }
      })
      if (duplicatePost) {
        throw new TRPCError({ code: 'BAD_REQUEST', message: 'Title already exists' })
      }

      const newPost = await prisma.post.create({
        data: {
          title: input.title,
          content: input.content,
          author: {
            connect: {
              id: session?.user.id
            }
          }
        },
      })
      //..

接著我們重新登入後新增post 如此一來不會有 UNAUTHORIZED error 了。

https://ithelp.ithome.com.tw/upload/images/20231005/20145677YuJuQag6eC.png

prisma studio 來看你會發現 post 就關聯到對應的 user了~
( 這邊圖很小讀者可以自行放大XD )

https://ithelp.ithome.com.tw/upload/images/20231005/20145677UY0llmsHkM.png
最後一步就是在 PostForm 加上 signup 功能。

export const PostForm = ({ updateSuccessCallBack }: PostFormProps) => {
 //..

  return (
      //..

        <Button type='submit' disabled={false} fullWidth>submit</Button>
     //..
  )
}

但你會發現登出後還是停留在 http://localhost:3000/posts ,那是因為我們還沒有做轉址,明天會繼續教讀者完善這功能~

https://ithelp.ithome.com.tw/upload/images/20231005/20145677vcGWJBezV6.png

以上大概是簡單說明 middleware 用法,明天我們就來優化一下 auth 其他功能期待一下吧~

repo

https://github.com/Danny101201/next_demo/tree/main

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day-020. 一些讓你看來很強的全端 TRPC 伴讀 -Next-Auth(CredentialsProvider)
下一篇
Day-022. 一些讓你看來很強的全端 TRPC 伴讀 -withAuth
系列文
一些讓你看來很強的全端- trcp 伴讀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言